package MusicLandscape.entities;

/**
 * This class represents an album as a concrete release of a specific artist.<br>
 * An album has a list of tracks, which, in this class, is implemented as a (singly) linked lists of tracks.
 *
 * @author Jonas Altrock (ew20b126@technikum-wien.at)
 * @version 1
 * @since ExerciseSheet04
 */
public class Album extends Release {
    /**
     * A single item of a linked list of tracks.<br>
     * A single list item consists of the primary data, in our case a track, and a reference to its successor,
     * which is another list item.
     */
    private static class TrackListItem {
        /**
         * A reference to the next item in the list.
         */
        TrackListItem next;

        /**
         * The primary data of this list item.
         */
        Track track;

        /**
         * Creates a list item from a track.<br>
         * Simply wraps a list item around a track, so it can be inserted into a linked list of tracks.
         * <p>
         * This list item does NOT maintain its own copy of the original track. It means a track in the list can be
         * modified from the caller who might still maintain a reference, however, the list structure is protected.
         *
         * @param t the track of this list item
         */
        public TrackListItem(Track t) {
            track = t;
        }
    }

    /**
     * The tracks of this album.<br>
     * More specifically this is the head of the linked list of tracks of this album.
     */
    private TrackListItem trackListHead;

    /**
     * Creates a default Album.<br>
     * A default album is a default release with an empty track list.
     */
    public Album() {
    }

    /**
     * Creates a copy of an album.<br>
     * All release parts of this album are copied as described in the release copy constructor.
     * <p>
     * The track list of this album contains (references to) the same tracks as the original, meaning tracks are not
     * deeply copied.
     *
     * @param orig the album to copy
     */
    public Album(Album orig) {
        super(orig);
        TrackListItem it = orig.trackListHead;

        if (it == null) {
            return;
        }

        trackListHead = new TrackListItem(it.track);

        while (it.next != null) {
            trackListHead.next = new TrackListItem(it.next.track);
            it = it.next;
        }
    }

    /**
     * Create an album with a specific title of a specific artist in a specific year.
     *
     * @param title  the title of the new album
     * @param artist the artist of the new album
     * @param year   the year of the new album
     */
    public Album(String title, Artist artist, int year) {
        super(title, artist, year);
    }


    /**
     * Adds a track to the list of tracks.<br>
     * Tracks are added to the end of the list.
     * <p>
     * Null tracks are not accepted. The method returns whether the list was modified.
     * true means success (track was added) false means no success (track was NOT added).
     *
     * @param t the track to add
     * @return whether the list was modified (added successfully) or not
     */
    public boolean addTrack(Track t) {
        if (t == null) {
            return false;
        }

        // no head = create new list
        if (trackListHead == null) {
            trackListHead = new TrackListItem(t);
            return true;
        }

        // iterate to end
        TrackListItem tail = trackListHead;

        while (tail.next != null) {
            tail = tail.next;
        }

        // append new list item
        tail.next = new TrackListItem(t);
        return true;
    }

    /**
     * Removes a track from the track from the list of tracks.
     * <p>
     * Removes and returns the track at position n from the list of tracks. Element numbering starts at 0, such that in
     * a list containing a single element the position of that element is 0 (zero). If the requested element does not
     * exist in the list null is returned.
     *
     * @param n the (zero-based) position of the track to be removed.
     * @return the removed track or null
     */
    public Track removeTrack(int n) {
        int i = 0;
        TrackListItem it = trackListHead;
        Track t;

        // empty list
        if (it == null) {
            return null;
        }

        // remove head
        if (n == i) {
            t = it.track;
            trackListHead = it.next;
            return t;
        }

        // find predecessor of n
        while (it != null && i < (n - 1)) {
            i++;
            it = it.next;
        }

        // has no successor = element at index n does not exist
        if (it == null || it.next == null) {
            return null;
        }

        // remove element n
        t = it.next.track;
        it.next = it.next.next;

        return t;
    }

    /**
     * Gets the number of tracks on this album.
     *
     * @return the number of tracks
     */
    public int nrTracks() {
        int n = 0;
        TrackListItem it = trackListHead;

        while (it != null) {
            n += 1;
            it = it.next;
        }

        return n;
    }

    /**
     * Gets the tracks of this album.
     * <p>
     * This method creates an array containing all tracks of this album preserving their current order. If the album
     * has no tracks, an array of size zero is returned. The tracks in the returned array are NOT (deep) copies of the
     * tracks currently maintained by this album, meaning that the caller can modify the tracks of this album, however
     * modification of their ordering in the list is not possible from outside.
     *
     * @return the tracks of this album in order
     */
    public Track[] getTracks() {
        Track[] tracks = new Track[nrTracks()];
        int i = 0;
        TrackListItem it = trackListHead;

        while (it != null) {
            tracks[i] = it.track;
            i += 1;
            it = it.next;
        }

        return tracks;
    }

    /**
     * Gets the total running time of this album.
     * <p>
     * The running time is the sum of the running times of all tracks in this album. The time is returned in seconds.
     *
     * @return the total running time in seconds.
     */
    public int totalTime() {
        int i = 0;
        int time = 0;
        TrackListItem it = trackListHead;

        while (it != null) {
            time += it.track.getDuration();
            i += 1;
            it = it.next;
        }

        return time;
    }

    /**
     * Gets a String representation of this album.
     * <p>
     * The String representation of an album adds the titles of all tracks to the release String representation.
     * The list of track names is enclosed by opening and closing brackets ([,]).
     * Track titles are also enclosed by opening and closing brackets.
     *
     * @return the string representation
     */
    public String toString() {
        StringBuilder sb = new StringBuilder("[");
        TrackListItem it = trackListHead;

        while (it != null) {
            sb.append("[").append(it.track.getTitle()).append("]");
            it = it.next;
        }

        sb.append("]");

        return super.toString() + "\n" + sb;
    }
}
